In this chapter, we discuss the processes involved in creating procedures in Visual Basic Script, along with different strategies in arranging those procedures. In an earlier chapter, I discussed the concept of limiting the scope of data and encapsulating functionality as much as possible. Here, I take the discussion a little further by developing strategies in the decisions that involve making procedures in what constitutes a well designed routine hierarchy. When you are finished with this chapter, you will have learned some new approaches to application design and perhaps even been stimulated to develop these ideas even further.
If you are coming entirely from an HTML background with little experience in a structured programming environment, like Visual Basic Script, this is the chapter for you. A procedure in Visual Basic Script is a structure with a single entry point and one or more exit points containing a series of statements that perform a particular task. Procedures are called by other lines of code to perform a given task and return the program flow back to the line that The procedure was called from. In most cases, procedures can look just like keywords and intrinsic functions when used and, in fact, work the same way.
The process of creating a procedure starts with identifying the reasons for a procedure. A procedureÆs primary purpose is to reduce the complexity of your program. Procedures should hide the detail information so you donÆt have think about those details when you are actually using those procedures. Other reasons for a procedure include code reuse, maintainability, and accuracy. You decide how the procedure will actually go about performing the task assigned to it, write the code for that procedure, and thoroughly test it for accuracy and volatility. The accuracy of the results derived from your procedure should be checked using the extreme ranges of your expected data. Find out what breaks your procedure, and then devise ways to prevent that procedure from being broken.
The following are good candidates for a procedure:
In Visual Basic Script, you declare a procedure by declaring it as a Sub or Function, and provide a unique name for the procedure, as well as any arguments that should be included. The basic syntax for declaring a procedure is:
[Private|Public] Sub|Function name (arg1, arg2,à)
à.procedure code
End [Sub|Function}
The [Private|Public] arguments are scope arguments. A public procedure can be seen by all procedures in all script modules. A private procedureÆs scope is limited to the script module that it is declared in. Both arguments are optional and the default is a public procedure.
The Sub|Function keywords define the type of procedure it is. Note that in Visual Basic Script there are no property procedures as in Visual Basic. The (arg1,arg2,...) portion is where you declare the arguments that are to be passed to your procedure. Here is a good place to talk about variable scope again. Global variables should be used as little as possible. It is much better programming practice to pass values to other procedures through the argument list. This way you know what data the procedure requires and what data the procedure modifies. Global variables, on the other hand, can be modified in any procedure any where in your code.
Many programmers like the convenience of global variables because they are easily accessible. Others, like myself, prefer to minimize the scope of a variable as much as possible so their purpose can be maintained and readily understood. When they are globally available you no longer have that maintainability and do not easily understand all the complexities that can be involved with that data. The End Sub or End Function statements mark the limits of the procedure.
A function type procedure is a block of code that takes arguments, processes those arguments, and then returns a value as part of its call. A function can also be used as part of an expression. Functions are mostly used to process information in a specified manner and return a result on the basis of the information. A function can return any of the data sub-types supported by the variant. Also, by default, arguments passed to a function are passed by reference so that if you change the value contained in an argument those changes are available to the procedure that called it. If you do not want the function to make changes to an argument that is passed back to the calling procedure then you have to declare the argument as ByVal. In this case, the argument is passed as a value rather than a pointer to the location where the data is stored (see Listing 20.1).
Listing 20.1 The Syntax for Declaring a Function Procedure
In the function example provided in Listing 20.1, you may notice there is some simple error trapping that checks to make sure the DateValue argument is, in fact, a date value. Another point you may notice is that when reading the function you learn exactly what the function does. This is an important feature to strive for. Name your procedures and variables with names that fully describe what they do or what they contain. You may understand your code when you write it, but when you come back to maintain it in a week, month, or even a year, youÆll appreciate being able to instantly recognize your intended logic thanks to adequate naming conventions.
A Subroutine (Sub) procedure differs from a function in that it does not return a value and cannot be used as part of an expression. Like the function procedure, it is a discrete block of code that is called from another procedure, and, when it is done, immediately returns to the next operation after the line that called it. The syntax of a Sub is as shown in Listing 20.2:
Listing 20.2 Syntax for Declaring a Subroutine
In Listing 20.2, you see two ways to invoke a subroutine. If you just use the SubÆs name you pass the argument without the parentheseses. If you use the Call keyword then you must surround the arguments with parenthesis. Again, you may notice that the naming convention of the Subroutine describes exactly what it does.
Calling procedures in Visual Basic Script work exactly the same as in Visual Basic. Functions are usually used as part of expressions, since they can return a value, and subroutines are used as statements.
To call a function procedure, you can either use it as part of an expression or use it like a subroutine ignoring the return value. If you use it as part of an expression, you must include the parentheses whether there are parameters to pass or not. If you do not use it as part of an expression then you must leave off the parentheses. Examples of both types of calls are given in Listing 20.3.
Listing 20.3 Using a Function in an Expression
Call is an optional argument used to transfer control to a subroutine or function. When used, it requires you to wrap the argument list in parentheses. When the Call statement is used with a function, the return value is discarded.using the call statement would look like this: Call ChangeRotation().
You may not think exiting procedures the subject of much discussion at first glance. But I have had some interesting discussions on this subject that revolve around the proper way to exit a procedure. I am a strong advocate of the single-entry, Single Exit rule that states there should only be a single point of entry in a procedure and a single point of exit. In Visual Basic, the single entry is enforced for all types of procedures. The single exit point, however, is another matter.
In Visual Basic, labels and the much-maligned GoTo statement allow a programmer to easily construct procedures that follow the single exit point rule. Visual Basic Script, however, does not utilize labels or GoTo. You can still have your procedures follow the single exit point, but this involves more complex logic in certain situations. I like having procedures that follow a consistent pattern with distinct logic, exit, and error handling sections. Visual Basic Script makes this a little difficult since error handling has to be inline; and if an error cannot be rectified in your procedure logic, you must provide your error exit code immediately after the error or concoct some unique and probably convoluted logic to delay the need to exit the procedure. If there is a way to correct an error in line, there is no way to repeat the line that caused the error except to add additional logic to duplicate the line if the error can be corrected. I sincerely hope that future versions of Visual Basic Script address this shortcoming.
The Exit function can be placed anywhere within your procedure to immediately exit the function and return control to the line that invoked it. The Exit Function statement is not required since once reaching the End Function line, the function is exited anyway. The Exit FunctionÆs most likely use would be to exit the function after an error had occurred or if some other special condition occured where you would want to immediatle exit the Function.
Procedure parameters are values that are passed to a procedure for processing by that procedure. Procedure parameters are also where a large portion of all program errors occur. The fact that Visual Basic Script is a weakly typed language makes the interface between procedures even more error prone since you can pass a string where a long is expected. There is no built-in parameter type checking when passing a value to a procedure. So the first order is to make sure that the actual parameters passed to the procedure match the formal parameters of the procedure declaration. Fortunately, Visual Basic Script comes equipped with a set of functions that checks the subtype of the parameter being passed. Table 20.1 enumerates those functions and describes what they do. To use them, you simply place the variant as an argument to the function and check for a true or false.
Table 20.1 Functions that Verify a Variable's Subtype
Name | Description |
IsArray() | Returns a Boolean indicating if variant is an array |
IsDate() | Returns a Boolean indicating if variant contains a date |
IsEmpty() | Returns a Boolean indicating if variant is uninitialized |
IsNull() | Returns a Boolean indicating if variant contains a Null |
IsNumeric() | Returns a Boolean indicating if variant contains a number |
IsObject() | Returns a Boolean indicating if variant contains an object |
In Listing 20.5, you may notice that I wrapped the functionality of the procedure in an If...Then...Else statement. This ensures that the procedureÆs code logic is run only if the parameter passed to the function is of a valid type. If the procedure is also sensitive to range values then you should also check for a valid range of values before proceeding with procedure logic. ThereÆs no sense in allowing a divide by zero or overflow error to stop your code. The type checking doesnÆt include checking for a string or for a long, double, or other specific number type. The number types automatically adjust to the values they contain. If you want to coerce a value to a particular type, you can do so using the conversion functions. If you really need to confirm that a variant contains a string, you have to do it through a process of eliminating the other possible types so that the only possible type left is a string.
Listing 20.5 Checking the Sub Type of a Variant
When declaring the procedureÆs parameters, use a consistent method of ordering them. Many of the experts, like Steve McConnel of Code Complete fame (Microsoft Press 1993), suggest the use of the input-modify-output order of arranging your parameter declarations. You can use an alphabetical, datatype, or other arrangement, but be consistent in your choice. The input-modify-output order implies that you place input only parameters first, modifiable parameters second, and output only parameters last. Also, if you are using the same parameters in several different procedures, make sure they are consistently placed in the same order so you donÆt get confused between them. DonÆt include arguments that are not going to be used by your procedure. If you decide that the parameter is not needed, then remove it from the declaration.
Visual Basic Script is a structured programming language with many weak parts in its design. Among the most obvious drawbacks is weakly typed variables. Another is the very limited options for error handling. If this was a full-blown programming environment it would be a nightmare to work with; but its target audience is the limited environment of a scripting language for Web browsers, servers, and other applications that require limited scripting capability. With that in mind, IÆll discuss some programming strategies using Visual Basic Script.
Encapsulation of your code and data as much as possible has the effect of containing logic and programming errors into discrete areas that can be more easily diagnosed and corrected. This is one of the main driving forces behind the OOP programming paradigm that has been permeating almost every aspect of the industry. Unfortunately, Visual Basic Script does not include the class/object creation capabilities of Visual Basic itself. Even more dangerous is that you can write code that runs outside of a procedure definition. This has the effect of letting you write a set of code that works every time you load the page, but can open the door to writing large inline code blocks without any structure at all. HTML is like this as well, but, with HTML, you have a very limited ability to do anything on the client side. With the introduction of scripting languages like Java, JavaScript, and Visual Basic Script, the limitations are rapidly disappearing.
For those coming from an HTML background and limited programming experience, you should begin adopting habits right now that make your scripts more readable and maintainable. Some of the key concepts of encapsulated code follow.
Hiding the internal workings of a procedure as much as possible from the rest of your application helps prevent you from abusing the purpose of that procedure and introducing errors. Once you design, debug, and test a procedure to produce a given result, leave it alone. If you need added functionality then write another procedure to produce that functionality. You can even wrap the original procedure within another procedure and modify its results to get the added functionality, but donÆt modify the original procedure itself if it is already being called by other procedures.
Another aid to encapsulation is loose coupling between procedures. This means that procedures should have a minimal dependency upon each other. A procedure should be entirely self dependent for its results and not rely on another procedureÆs validity. DonÆt assume that a procedure you are calling will always work. Later modifications to another procedure can introduce errors in your procedure. If your procedure requires arguments then check those arguments for proper value range and type, and introduce a graceful exit for that procedure if the arguments do not meet specification. Conversely, if you are calling a procedure whose results your procedure depends upon, then include code that protects your procedure should the procedures you are calling fail. If you are calling functions that are returning values then also check those values for proper range and type. If your procedure is supposed to supply a value then make sure that it returns a value of some sort regardless of whether it succeeds or fails in its purpose. In Visual Basic Script, you can convert a functionÆs return value to an error value to indicate that an error has occurred in your procedure.
Another, very important aspect of design for your Visual Basic Scripts is using naming conventions that clearly explain what your code does. As an example, look at Listing 20.7. Obviously, this procedure is the click event of a control. The two lines of code contained in the procedure donÆt do much in the way of explaining themselves. Can you recognize their purpose?
Listing 20.7 CH9_1.HTMùPoor Procedure Naming Practices
Okay, if you canÆt be sure what these lines of code do then maybe we should look at the underlying code within these two procedure calls. Listing 20.8 contains the code for both procedures. The first procedure, ChgLblDrctn doesnÆt tell you much except that it reverses a value from a positive to a negative and back again. There is No indicationof what the value is used for though Looking at the next procedure, upon seeing the lbl, you can deduce that it changes the forecolor of a label.
Well, thatÆs what the procedure does all right, but can you tell me what colors it changes the forecolor to? This procedure is using what many programmers refer to as magic numbers! Magic numbers are literal values that magically accomplish a purpose without you knowing what or how. If you understand the RGB palette system that Windows uses then you recognize that these magic numbers are the three primary colors of red, green, blue, and also black. Even if you figure out the procedureÆs entire purpose and the meanings of the parameters it uses, you must interupt your reading of the code to think about it. Now imagine having to debug several hundred lines of code written in this style.
Listing 20.8 CH9_1.HTMùMore Poor Naming Practices
Now letÆs look at the same procedures done with informative names and naming conventions. Listing 20.9 shows the labelÆs click event calling procedures with informative names. The first procedure changes the direction of the label. The second procedure leaves no doubt that its purpose is to change the labelÆs color.
Listing 20.9 CH9_1.HTMùThe Right Way to Name Procedures
Okay, we really donÆt need to look any further because we already know what the procedures do to a fair degree. But I want to know what colors it changes to and what direction is being changed (see Listing 20.10).
Listing 20.10 CH9_1.HTMùMore Self Documenting Code!
Looking at the ChangeLabelDirection procedure I see a variable named lblRotation, which leaves me with no doubt that the purpose of the procedure is to change the rotation direction of the label. The second procedure lets me know what colors are being used because the magic numbers are replaced with descriptive constants. Notice that the constants are all uppercase with an underscore between the prefix and the color name. Altogether, this is what you call self documenting code. This is part of a naming convention that I use and is similar to the one that Microsoft suggests.
You can use any convention you want including one you devise on your own. Just use it consistently and document the rules of your naming conventions so that others using your code can understand what you are doing. Listing 20.11 shows a code header in a script module that explains to the reader what conventions are being used in the accompanying code.
Listing 20.11 CH9_1.HTMùDocumenting Your Naming Conventions
When deciding where to place a certain functionality into a procedure you first must understand what a procedure is or should be. A procedure should be a block of code that is called upon for a single purpose. That procedure may call several other procedures to accomplish its purpose, but its very existence should only be for that single purpose. When you have recognized the need for a discrete procedure, then you have the justification for a procedure. A discrete procedure can be identified as any purpose that includes one of more of the following items:
Some might argue the preceding list, but these have usually been the best arguments for creating a discreet function or subroutine. Your procedures should act like a black box in that the calling procedure should only know what it does, not how it does it. If you go back to Listing 20.9 and look at the sprlbl1_Click event, you not only fully understand what each of the contained procedures do, you are not confronted with any complex logic either. The complexity is kept within the procedures that actually do the discrete tasks required. From that, you build the next layer that controls the order and circumstances that these discrete tasks are called. Even further up the ladder of the hierarchy might be the user interface that responds to user events.
In the case of this example of Visual Basic Script being used with an HTML document, the HTML code actually provides the upper hierarchy portion of this design. You can design your Visual Basic Scripts to interact as a single layer under the HTML code but you soon find the more complex pages become near impossible to maintain. Design your VBScript scripts to take full advantage of the encapsulation techniques available and break discreet functionality into maintainable procedures.
The black box procedure.
I started to say that you should use functions when a return value is expected and subroutines when no return value is expected. While true enough, it begs a much more precise response. Whether your procedure should be a subroutine or function can be directly tied to whether the calling procedure is dependent on the success or failure of the procedure itÆs calling. That is to say, if the procedureÆs failure does not adversely affect the calling procedureÆs purpose, then a subroutine is adequate. But if the calling procedure requires the procedure to succeed in order for itself to succeed then it must be a function.
LetÆs add another functionality to our rotating label project. I want to control the speed of the rotation by entering a number that represents the degrees of rotation with every clock tick. I need a textbox in which to enter the desired speed and a button control to register the speed change. I call the textbox txtDegrees and the button btnSpeed. The idea is that I enter some value into the textbox, click the button, and, in the click event, I change the value of lngRotate to reflect that new value (see Listing 20.12).
Listing 20.12 CH9_1.HTMùShould This Be a Discrete Procedure?
ItÆs simple, only one line long, and understandable. So what is wrong with this code? The functionality can introduce errors into the program! What happens if the user enters a number out of the accepted range? What happens if the user enters a non-numeric value? You generate an error and bring the script to a halt. Now remember, you donÆt want to clutter up the second layer of code with the complexity of a discrete process so you create a procedure to handle this. I donÆt want to pass too much information to the procedure so I limit it to just the value of the txtDegrees and expect it to return a valid value for lblRotation (see Listing 20.13).
Listing 20.13 CH9_1.HTMùMoving Things to a Discrete Procedure
This function procedure first checks to see if the argument is of the right data type and, if it is, checks to see if the value of the argument is within a specified range. In essence, no matter what the user enters into the textbox this function returns a valid value that allows the purpose of the application to continue. The possible errors are contained within this procedure and not allowed to percolate upwards in the hierarchy. You now have a function procedure that performs the required process. Do you know what the process it performs is? ItÆs not the changing of the rotation speed. ThatÆs the purpose of the click event. The purpose is to validate the userÆs data entry. Listing 20.14 shows the click event using our function procedure.
Listing 20.14 CH9_1ùClick Event with Validated User Input
Once again you can see the effects of self-documenting code. At a glance, you can see that the click event sets the rotation speed of the label after checking the validity of the value in txtDegrees. The idea is to design your procedures so they are as bulletproof as possible and completely safe from external circumstances crashing them. The modified Label rotation example is illustrated in Figure 20.2.
The rotating label example with the speed modification.
Tired of rotating labels yet? Okay, letÆs move on to some serious application development. In Chapters 12 and 13 we develop a database application that allows the user to browse or search a catalog of products and make an order.
We can start to lay out the overall design for the product catalog here. I base this example on a database model that has equivalents in both Access and SQL Server 6.5, but I refer to the publishers demo database example in SQL Server 6. If you only have VB4 or Access then you have to use the BIBLIO.MDB database and modify the table names accordingly as this project takes shape.
An HTML document already has clearly defined divisions within it. There is the head, body, or frames. The head section of an HTML document is used to describe that document. The body contains the active content of the document such as forms, objects, and VBScript. How to use these areas is explained in Chapter 3. The Visual Basic Script area of the document is usually the last group of sections in the document. There are no hard coded rules that require this, but good organization of your HTML document probably causes you to gravitate towards this convention anyway. YouÆll also see that I further divide the scripting area into three functional sections that help to make your code more understandable by subsequent editors (see fig. 20.3). Remember, you just might be one of those subsequent editors that is trying to remember why you did what you did.
Organizing your application's Visual Basic Scripts.
In any case, coming from a Visual Basic background, IÆve become accustomed to the way things are organized in that environment, and it is heavily influencing the suggestions I make here. As your scripts become more involved the initial organization presented here will go a long way towards helping you keep a handle on things.
In Visual Basic, the General Declarations area is where all module-wide variables, DLL declarations, and object references are declared. In Visual Basic Script, I am suggesting that this practice also be followed. You may also want to provide a header that describes your naming conventions, as shown in listing 19.13. While Visual Basic Script only has the variant type available, it is still a good idea to identify the subtype of the value it contains by using meaningful prefixes. Visual Basic Script does not directly support constants, but you can create and clearly identify constants using one of two conventions. The first is to use all uppercase characters and separate words with an underscore, which is the convention I use in this book. Another valid convention is to use the prefix cst to identify constants. What is important is that you do adopt a consistent method of documentation.
Besides using this area as the place for declarations, you can also define initialization code. Unlike Visual Basic, where you must place all code within a defined procedure, Visual Basic Script allows you to place code outside of procedures. The code you place outside of a procedure runs every time the document is loaded into the browser (see Listing 20.15). If you need to utilize this feature, it is best to place that code in the General Declarations area script so its presence is immediately recognized later by you or someone else who might need to understand or edit the document. Another advantage to consider is you can develop a declarations area script that is generic and can be used in all your projects with slight modification.
Listing 20.15 SCRIPTTEMPLATE.TXTùSuggested Outline of a Declarations Area Script
Event procedures are usually called through the actions of the user such as a clicking a button, selecting an item from a combo box, or entering text into a text field. There are also other events like the Timer_Time event that is program controlled or the VRML collision event; these are independent of user interaction. Regardless of what the event is, they should be placed in a script whose purpose is to only contain events. Within the event procedures you can call the user defined procedures that produce the desired reaction to those events. Very little actual computational code should be placed at this level. Think of the Event level scripts as the medium for controlling logic flow, not as the place for the actual computational logic.
An observer should be able to look at your event procedures and understand exactly what they do without having to spend time figuring out the arcane logic of your computational code. Listing 20.10 shows an event procedure with one line of code that simply sets the value of rotation according to the checked value of a textbox. There is underlying logic that checks the value of that textbox for type and range, and returns a safe value for the lngRotation value; but all of that is hidden in a detail procedure that you donÆt need to see to understand the event procedureÆs purpose. Its purpose is to change the value of the rotation rate according to what is in the textbox.
This section should contain all the detail procedures that actually perform calculations, control graphic renderings, submit forms, and all other detail logic of your documentÆs program. You should not include any event procedures here at all. Your procedures at this level should provide for error trapping and handling to make sure that generated errors do not percolate upward enough to lock up the browser or even stop the documentÆs functionality. This is a very important concept to consider.
Many of the HTML documents you author will be used by people who are not very computer literate. The computer and the Internet are rapidly becoming a household commodity with the inherent lack of computer experience many of these new users have. A lockup or even a non-responsive Web page frustrates and even upsets many who are not at all cognizant of what is going on. ThatÆs not a fault of the user, itÆs a fault of the technology that didnÆt insulate them from these potential problems.
Write your detail procedures so they are capable of handling any logic or user error. Design into your logic a way to degrade gracefully without unduly alarming the user of your page. And, finally, if there is no other alternative, then inform him that an error has occurred through a message box and suggest to him how to recover.
Procedures that return a value should always check to ensure that the value they return is within reasonable limits given their purpose.
In this chapter I went over the syntax of creating function and subroutine procedures. If you are an experienced programmer, you will immediately miss the ability to alter the values of arguments passed to a procedure. It seems that the only work around to this is to allow your variables a wider scope that crosses procedure boundaries. Be sure to incorporate adequate type and bounds checking within you procedures to account for this.
The proposal to arrange your code into three levels of Script, Initialization, Event, and Utility is not a hard wired rule that you must follow. It is more a suggestion that you adopt or develop an organizational and naming convention that helps to make your scripts easier to understand and maintain.
| Previous Chapter | Next Chapter |
| Search | Table of Contents | Book Home Page | Buy This Book |
| Que Home Page | Digital Bookshelf | Disclaimer |
To order books from QUE, call us at 800-716-0044 or 317-361-5400.
For comments or technical support for our books and software, select Talk to Us.
© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.